﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Delta.Engine
{
    public class Processor
    {
        private IContentMap _contentMap1;
        private IContentMap _contentMap2;
        private List<DeltaResult> _deltaList;
        private DeltaStateList _deltaStateList;

        public Processor()
        {
            _contentMap1 = null;
            _contentMap2 = null;
            _deltaList = null;
            _deltaStateList = null;
        }

        public void Process(IContentMap contentMap1, IContentMap contentMap2)
        {
            _contentMap1 = contentMap1;
            _contentMap2 = contentMap2;
            _deltaList = new List<DeltaResult>();

            int map1Count = contentMap1.Count();
            int map2Count = contentMap2.Count();

            if ((map2Count > 0) && (map1Count > 0))
            {
                _deltaStateList = new DeltaStateList(map2Count);
                processRange(0, map2Count - 1, 0, map1Count - 1);
            }

        }

        private void processRange(int map2Start, int map2End, int map1Start, int map1End)
        {
            int index = -1;
            int length = -1;
            int maxLength = 0;
            DeltaState current = null;
            DeltaState best = null;

            for (int map2Index = map2Start; map2Index <= map2End; map2Index++)
            {
                maxLength = (map2End - map2Index) + 1;
                if (maxLength <= length)
                {
                    break;
                }

                current = _deltaStateList.GetByIndex(map2Index);

                if (!current.HasValidLength(map1Start, map1End, maxLength))
                {
                    getLongestMatch(current, map2Index, map2End, map1Start, map1End);
                }

                if (current.Status == DeltaStatus.Matched)
                {
                    if (current.Length > length)
                    {
                        index = map2Index;
                        length = current.Length;
                        best = current;
                    }

                    map2Index += current.Length - 1;
                }
            }
            if (index < 0)
            {
                //no matches
            }
            else
            {
                int start = best.StartIndex;
                _deltaList.Add(DeltaResult.AddNewMatchResult(index, start, length));

                if (map2Start < index)
                {
                    if (map1Start < start)
                    {
                        processRange(map2Start, index - 1, map1Start, start - 1);
                    }
                }

                int upperMap2Start = index + length;
                int upperMap1Start = start + length;
                if (map2End > upperMap2Start)
                {
                    if (map1End > upperMap1Start)
                    {
                        processRange(upperMap2Start, map2End, upperMap1Start, map1End);
                    }
                }
            }
        }

        private int getMatchLength(int map2Index, int map1Index, int maxLength)
        {
            int count;
            for (count = 0; count < maxLength; count ++)
            {
                if (_contentMap2.GetByIndex(map2Index + count).CompareTo(_contentMap1.GetByIndex(map1Index + count)) !=0)
                {
                    break;
                }
            }

            return count;
        }

        private void getLongestMatch(DeltaState current, int map2Index, int map2End, int map1Start, int map1End)
        {
            int maxMap2Length = (map2End - map2Index) + 1;
            int currentLength = 0;
            int bestLength = 0;
            int bestIndex = -1;
            int maxLength = 0;

            for (int map1Index = map1Start; map1Index <= map1End; map1Index++)
            {
                maxLength = Math.Min(maxMap2Length, (map1End - map1Index) + 1);

                if (maxLength <= bestLength)
                {
                    break;
                }
                currentLength = getMatchLength(map2Index, map1Index, maxLength);
                if (currentLength > bestLength)
                {
                    bestIndex = map1Index;
                    bestLength = currentLength;
                }
                map1Index += bestLength;
            }

            if (bestIndex == -1)
            {
                current.SetNoMatch();
            }
            else
            {
                current.SetMatch(bestIndex, bestLength);
            }
        }

        private bool appendBlock(List<DeltaResult> report, int currentMap2, int nextMap2, int currentMap1, int nextMap1)
        {
            bool retval = false;
            int map2 = nextMap2 - currentMap2;
            int map1 = nextMap1 - currentMap1;
            int minCompare = 0;

            if (map2 > 0)
            {
                if (map1 > 0)
                {
                    minCompare = Math.Min(map2, map1);
                    report.Add(DeltaResult.AddNewReplaceMap1Result(currentMap2, currentMap1, minCompare));
                    if (map2 > map1)
                    {
                        currentMap2 += minCompare;
                        report.Add(DeltaResult.AddNewAddMap2Result(currentMap2, map2 - map1));
                    }
                    else
                    {
                        if (map1 > map2)
                        {
                            currentMap1 += minCompare;
                            report.Add(DeltaResult.AddNewDeleteMap1Result(currentMap1, map1 - map2));
                        }
                    }
                }
                else
                {
                    report.Add(DeltaResult.AddNewAddMap2Result(currentMap2, map2));
                }
                retval = true;
            }
            else
            {
                if (map1 > 0)
                {
                    report.Add(DeltaResult.AddNewDeleteMap1Result(currentMap1, map1));
                    retval = true;
                }
            }
            return retval;
        
        }

        public List<DeltaResult> Result()
        {
            List<DeltaResult> retval = new List<DeltaResult>();
            int map2cnt = _contentMap2.Count();
            int map1cnt = _contentMap1.Count();

            if (map2cnt == 0)
            {
                if (map1cnt > 0)
                {
                    retval.Add(DeltaResult.AddNewDeleteMap1Result(0, map1cnt));
                }
                return retval;
            }
            else
            {
                if (map1cnt == 0)
                {
                    retval.Add(DeltaResult.AddNewAddMap2Result(0, map2cnt));
                    return retval;
                }
            }

            _deltaList.Sort();

            int currentMap2 = 0;
            int currentMap1 = 0;
            DeltaResult last = null;

            foreach (DeltaResult item in _deltaList)
            {
                if ((!appendBlock(retval, currentMap2, item.Map2IDX, currentMap1, item.Map1IDX)) && (last != null))
                {
                    last.AddLength(item.Length);
                }
                else
                {
                    retval.Add(item);
                }

                currentMap2 = item.Map2IDX + item.Length;
                currentMap1 = item.Map1IDX + item.Length;
                last = item;
            }
            

            appendBlock(retval, currentMap2, map2cnt, currentMap1, map1cnt);

            return retval;
        }

    }
}
